﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Resources;
using System.IO;
using System.Xml.Linq;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Net.Browser;
using System.Windows.Threading;
using System.Threading;
using System.Security;

namespace ControlExplorer
{
    public class C1AssemblyLoaderExceptionEventArgs : EventArgs
    {
        public Exception Exception { get; set; }
    }

    public class C1AssemblyLoaderLoadedEventArgs : EventArgs
    {
        public Assembly Assembly { get; set; }
        public long DownloadedBytes { get; set; }
    }

    public static class C1AssemblyLoaderManager
    {
        public static void Load(C1AssemblyLoaderInfo info, EventHandler<ProgressChangedEventArgs> handlerProgressChanged, EventHandler<C1AssemblyLoaderLoadedEventArgs> handlerLoaded, EventHandler<C1AssemblyLoaderExceptionEventArgs> handlerError)
        {
            var loader = new C1AssemblyLoader()
            {
                AssemblyName = info.AssemblyName,
                PackageUri = info.PackageUri,
            };
            loader.Loaded += (s, e) =>
            {
                info.DownloadedBytes = e.DownloadedBytes;
                info.Status = C1AssemblyLoaderStatus.Successful;
                info.EndTime = DateTime.Now;
                handlerLoaded(s, e);
            };
            loader.Error += (s, e) =>
            {
                info.DownloadedBytes = 0;
                info.Status = C1AssemblyLoaderStatus.Error;
                info.EndTime = DateTime.Now;
                handlerError(s, e);
            };
            loader.ProgressChanged += (s, e) =>
            {
                info.EndTime = DateTime.Now;
                handlerProgressChanged(s, e);
            };
            Requests.Add(info);
            loader.Load();
        }

        public static System.Collections.ObjectModel.ObservableCollection<C1AssemblyLoaderInfo> Requests =  new System.Collections.ObjectModel.ObservableCollection<C1AssemblyLoaderInfo>();


        class C1AssemblyLoader
        {
            // in
            public event EventHandler<ProgressChangedEventArgs> ProgressChanged;
            public event EventHandler<C1AssemblyLoaderLoadedEventArgs> Loaded;
            public event EventHandler<C1AssemblyLoaderExceptionEventArgs> Error;
            public string AssemblyName { get; set; }
            public Uri PackageUri { get; set; }

            // out
            Assembly Assembly { get; set; }

            static Dictionary<string, Assembly> _alreadyLoaded = new Dictionary<string, Assembly>();

            // invariant: _parts.Count == _partsDone + _partsBusy
            List<Part> _parts = new List<Part>();
            int _partsDone;
            int _partsBusy;

            bool _cancelled;
            Dispatcher _dispatcher;
            long _xapAndZipsBytesReceived;
            long _xapAndZipsTotalBytesToReceive;

            class Part
            {
                public long _bytesReceived;
                public long _totalBytesToReceive;
                WebClient _webClient;
                WebRequest _contentLengthRequest;
                Uri _uri;
                C1AssemblyLoader _loader;

                public Part(C1AssemblyLoader loader, Uri uri)
                {
                    _loader = loader;
                    _uri = uri;
                    _webClient = new WebClient();
                    _webClient.DownloadProgressChanged += DownloadProgressChanged;
                    _webClient.OpenReadCompleted += OpenReadCompleted;

                    _loader._parts.Add(this);
                    _loader._partsBusy++;
                }

                public void RequestBody()
                {
                    _webClient.OpenReadAsync(_uri);
                }
                public void RequestLength()
                {
                    _contentLengthRequest = WebRequestCreator.ClientHttp.Create(_uri);
                    _contentLengthRequest.Method = "HEAD";
                    _contentLengthRequest.BeginGetResponse(GetResponseCallback, null);
                }

                void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
                {
                    if (_totalBytesToReceive == 0)
                        Debug.WriteLine("DownloadProgressChanged " + _uri + " - " + e.TotalBytesToReceive);
                    UpdateSubTotal(e.BytesReceived, ref _bytesReceived, ref _loader._xapAndZipsBytesReceived);
                    UpdateSubTotal(e.TotalBytesToReceive, ref _totalBytesToReceive, ref _loader._xapAndZipsTotalBytesToReceive);
                    _loader.OnProgressChanged();
                }
                void OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
                {
                    if (_loader._cancelled)
                    {
                        return;
                    }

                    if (e.Error != null)
                    {
                        _loader._cancelled = true;
                        foreach (var d in _loader._parts)
                        {
                            d._webClient.CancelAsync();
                        }
                        _loader.OnError(e.Error);
                        return;
                    }

                    _loader._partsBusy--;
                    _loader._partsDone++;

                    StreamResourceInfo sri = new StreamResourceInfo(e.Result, null);

                    var manifestStreamInfo = Application.GetResourceStream(
                        sri,
                        new Uri("AppManifest.xaml", UriKind.Relative));

                    if (manifestStreamInfo != null)
                    {
                        XElement root = XDocument.Load(manifestStreamInfo.Stream).Root;
                        XNamespace ns = "http://schemas.microsoft.com/client/2007/deployment";
                        XNamespace x = "http://schemas.microsoft.com/winfx/2006/xaml";

                        var dllNames = root
                            .Elements(ns + "Deployment.Parts")
                            .Elements(ns + "AssemblyPart")
                            .Select(part => part.Attribute("Source").Value);
                        foreach (string dllName in dllNames)
                        {
                            StreamResourceInfo streamInfo = Application.GetResourceStream(
                                sri,
                                new Uri(dllName, UriKind.Relative));
                            Assembly a = new AssemblyPart().Load(streamInfo.Stream);
                            if (a != null && a.FullName.Split(',')[0] == _loader.AssemblyName)
                            {
                                _loader.Assembly = a;
                            }
                        }

                        var externalParts = root
                            .Elements(ns + "Deployment.ExternalParts")
                            .Elements(ns + "ExtensionPart");
                        var childParts = new List<Part>();
                        foreach (XElement part in externalParts)
                        {
                            Part data = new Part(_loader, new Uri(part.Attribute("Source").Value, UriKind.RelativeOrAbsolute));

                            // first we ask for all the parts lengths, to have a better progress estimate
                            data.RequestLength();

                            childParts.Add(data);
                        }
                        foreach (Part data in childParts)
                        {
                            //  we ask for the data proper after all the calls to RequestLength
                            data.RequestBody();
                        }
                    }
                    else
                    {
                        // We assume the .zip contains only one assembly, named as the .zip file.
                        StreamResourceInfo streamInfo = Application.GetResourceStream(
                            sri,
                            new Uri(System.IO.Path.GetFileNameWithoutExtension(_uri.ToString()) + ".dll", UriKind.Relative));
                        var a = new AssemblyPart().Load(streamInfo.Stream);
                        if (a != null && a.FullName.Split(',')[0] == _loader.AssemblyName)
                        {
                            _loader.Assembly = a;
                        }
                    }

                    if (_loader.Assembly == null)
                    {
                        _loader.OnError(new InvalidOperationException("Cannot load specified dll."));
                        return;
                    }

                    if (_loader._partsBusy == 0)
                    {
                        _alreadyLoaded[_loader.AssemblyName] = _loader.Assembly;
                        _loader.OnLoaded(_loader.Assembly, _loader._xapAndZipsBytesReceived);
                    }
                }
                void GetResponseCallback(IAsyncResult ar)
                {
                    _loader._dispatcher.BeginInvoke(() =>
                    {
                        WebResponse response;
                        try
                        {
                            response = _contentLengthRequest.EndGetResponse(ar);
                        }
                        catch (WebException ex)
                        {
                            return;
                        }
                        //response.ContentLength returns 0 for HEAD, so we read it manually
                        long ContentLength = long.Parse(response.Headers[HttpRequestHeader.ContentLength]);
                        Debug.WriteLine("GetResponseCallback " + _uri + " - " + ContentLength);
                        UpdateSubTotal(ContentLength, ref _totalBytesToReceive, ref _loader._xapAndZipsTotalBytesToReceive);
                        _loader.OnProgressChanged();
                    });
                }
            }

            public void Load()
            {
                if (_dispatcher != null)
                {
                    throw new InvalidOperationException("Call Load() only once");
                }
                _dispatcher = Application.Current.RootVisual.Dispatcher;

                Assembly assembly;
                if (_alreadyLoaded.TryGetValue(AssemblyName, out assembly))
                {
                    _dispatcher.BeginInvoke(() => OnLoaded(assembly, _xapAndZipsBytesReceived));
                    return;
                }

                new Part(this, PackageUri).RequestBody();
            }
            static void UpdateSubTotal(long newSubTotal, ref long subTotal, ref long total)
            {
                total += newSubTotal - subTotal;
                subTotal = newSubTotal;
            }
            void OnProgressChanged()
            {
                if (_cancelled)
                    return;
                var copy = ProgressChanged;
                if (copy != null)
                {
                    long percentage = 0;

                    if (_parts.Count > 1)
                    {
                        // all but the first
                        for (int i = 1; i < _parts.Count; ++i)
                        {
                            // add 1 to prevent divide by zero
                            percentage += 50 * _parts[i]._bytesReceived / (1 + _parts[i]._totalBytesToReceive);
                        }
                        percentage /= (_parts.Count - 1);
                    }

                    percentage += 50 * _parts[0]._bytesReceived / (1 + _parts[0]._totalBytesToReceive);
                    copy(this, new ProgressChangedEventArgs((int)percentage, null));
                }
            }
            void OnLoaded(Assembly assembly, long downloadedBytes)
            {
                var copy = Loaded;
                if (copy != null)
                {
                    copy(this, new C1AssemblyLoaderLoadedEventArgs() { Assembly = assembly, DownloadedBytes = downloadedBytes });
                }
            }
            void OnError(Exception e)
            {
                var copy = Error;
                if (copy != null)
                {
                    copy(this, new C1AssemblyLoaderExceptionEventArgs { Exception = e });
                }
            }
        }
    }

    public class C1AssemblyLoaderInfo
        : INotifyPropertyChanged
    {
        public C1AssemblyLoaderInfo() 
        {
            StartTime = DateTime.Now;
            Status = C1AssemblyLoaderStatus.Requested;
        }

        private long _downloadedBytes;
        public long DownloadedBytes 
        {
            get 
            {
                return _downloadedBytes;
            }
            set 
            {
                _downloadedBytes = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("DownloadedBytes"));
            }
        }

        private string _assemblyName;
        public string AssemblyName 
        {
            get 
            {
                return _assemblyName;
            }
            set 
            {
                _assemblyName = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("AssemblyName"));
            }
        }

        private Uri _packageUri;
        public Uri PackageUri 
        {
            get 
            {
                return _packageUri;
            }
            set 
            {
                _packageUri = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("PackageUri"));
            }
        }

        private C1AssemblyLoaderStatus _status;
        public C1AssemblyLoaderStatus Status 
        {
            get 
            {
                return _status;
            }
            set 
            {
                _status = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Status"));
            }
        }

        private DateTime _startTime;
        public DateTime StartTime 
        {
            get 
            {
                return _startTime;
            }
            set 
            {
                _startTime = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(""));
            }
        }

        private DateTime _endTime;
        public DateTime EndTime
        {
            get
            {
                return _endTime;
            }
            set
            {
                _endTime = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(""));
            }
        }
        public TimeSpan ElapsedTime 
        {
            get 
            {
                return TimeSpan.FromTicks(EndTime.Ticks - StartTime.Ticks);
            }
        }


        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }

    public enum C1AssemblyLoaderStatus 
    {
        Requested, 
        Successful,
        Error
    }
}
